package com.doule.farmerassistant;

import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.AsyncHttpClient.WebSocketConnectCallback;
import com.koushikdutta.async.http.AsyncHttpGet;
import com.koushikdutta.async.http.WebSocket;
import com.koushikdutta.async.http.WebSocket.StringCallback;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * Service Connect to server.
 * 
 * @author riguang
 * 
 */
public final class WebSocketService extends Service implements
        WebSocketConnectCallback, CompletedCallback, StringCallback {

    /**
     * JSON listener.
     * @author Happy
     *
     */
    public interface NotifyListener {
        /**
         * JSON string.
         * @param jobject json object
         */
        void onJSONNotify(final JSONObject jobject);
    }
    
    
    /**
     * listener list.
     * @author Happy
     *
     */
    public class NotifyListenerList {
        /**
         * list.
         */
        private ArrayList<NotifyListener> mList = new ArrayList<NotifyListener>();
        
        /**
         * get list.
         * @return list
         */
        public ArrayList<NotifyListener> getList() {
            return mList;
        }
        /**
         * add listener.
         * @param listener listener
         */
        public void addListener(NotifyListener listener) {
            if (isExist(listener)) {
                return;
            }
            
            mList.add(listener);
        }
        
        /**
         * remove listener.
         * @param listener listener
         */
        public void removeListener(NotifyListener listener) {
            mList.remove(listener);
        }
        
        /**
         * check whether a listener is exist or not.
         * @param lll listener
         * @return boolean
         */
        private boolean isExist(NotifyListener lll) {
            for (NotifyListener  ll :mList) {
               if (ll == lll) {
                   return true;
               }
            }
            
            return false;
        }
    }
    /**
     * password.
     */
    private String mPassword;
    /**
     * account.
     */
    private String mAccount;
    /**
     * constructor.
     */
    public WebSocketService() {
    }

    @Override
    public IBinder onBind(final Intent intent) {
        // not support binding
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {

    }

    /**
     * save account.
     */
    private void save2Pref() {
        SharedPreferences share = getSharedPreferences("SVACCOUNT", MODE_PRIVATE);
        SharedPreferences.Editor edit = share.edit();
        edit.putString("account", mAccount);
        edit.putString("password", mPassword);
        edit.commit();
    }
    /**
     * read account.
     */
    private void readPref() {
        SharedPreferences share = getSharedPreferences("SVACCOUNT", MODE_PRIVATE);
        mAccount = share.getString("account", "");
        mPassword = share.getString("password", "");
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // start working thread
        mMyService = this;
        
        if (intent != null) {
            mAccount = intent.getStringExtra("account");
            mPassword = intent.getStringExtra("password");
            save2Pref();
        } else {
            readPref();
        }

        closeWebSocket();
        buildServerConnection();
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }
    
    @Override
    public void onDestroy() {
        closeWebSocket();
    }


    /**
     * retry period when connect failed.
     */
    public static final int RETRY_PERIOD = 10 * 1000;
    /**
     * log tag.
     */
    private static final String TAG = "WebSocketService";

    /**
     * indicate current status is connecting to server.
     */
    public static final int STATUS_CONNECTING = 1;
    /**
     * indicate current status is failed to connect to server.
     */
    public static final int STATUS_CONNECT_FAILED = 2;
    /**
     * indicate current status is succeed connect to server.
     */
    public static final int STATUS_CONNECT_OK = 3;

    /**
     * work handler object, serialize all requests into the work thread.
     */
    private final Handler mWorkHandler = new Handler();

    /**
     * listener map.
     */
    private final HashMap<String, NotifyListenerList> mNotifyListeners = new HashMap<String, NotifyListenerList>();
    
    /**
     * WebSocket instance.
     */
    private WebSocket mWebSocket;

    /**
     * web socket url.
     */
    // private String mWebSocketUri;

    /**
     * connect web socket to server.
     * 
     * @param url
     *            server url
     */
    private void startWebSocket(final String url) {
        if (url == null) {
            Log.e(TAG, "startWebSocket failed, url is null");
            return;
        }
        
        if (mAccount == null || mAccount.length() < 1) {
            Log.e(TAG, "startWebSocket failed, account is null");
            return;
        }
        
        if (mPassword == null || mPassword.length() < 1) {
            Log.e(TAG, "startWebSocket failed, password is null");
            return;
        }
        
        if (mFutureWebSocket != null) {
            Log.e(TAG, "startWebSocket failed: a connection is begin, wait it to complete!");
            return;
        }
        
        Log.e(TAG, "startWebSocket url: " + url);
        
        AsyncHttpGet get = new AsyncHttpGet(url);
        get.addHeader("account", mAccount);
        get.addHeader("password", mPassword);
                
        mFutureWebSocket = AsyncHttpClient.getDefaultInstance().websocket(get,
                null, this);
    }

    /**
     * close web socket connection.
     */
    private void closeWebSocket() {
        if (mWebSocket != null) {
            mWebSocket.setClosedCallback(null);
            // mWebSocket.setDataCallback(null);
            mWebSocket.setStringCallback(null);

            mWebSocket.close();
        }

        mWebSocket = null;
    }

    /**
     * static singleton instance.
     */
    private static WebSocketService mMyService;
    /**
     * WebSocket connection future.
     */
    private Future<WebSocket> mFutureWebSocket;

    /**
     * cancel websocket connection attempt.
     */
    private void cancelFutureWebSocket() {
        if (mFutureWebSocket != null) {
            mFutureWebSocket.cancel();
            mFutureWebSocket = null;
        }
    }

    /**
     * get singleton instance.
     * 
     * @return lanService
     */
    public static WebSocketService getInstance() {
        return mMyService;
    }

    /**
     * callback when web socket connected completed.
     * 
     * @param arg0
     *            exception object
     * @param arg1
     *            web socket object
     */
    @Override
    public void onCompleted(final Exception arg0, final WebSocket arg1) {
        cancelFutureWebSocket();
        if ((arg0 != null) || (arg1 == null)) {
            Log.e(TAG, "websocket connect error:");
            if (arg0 != null) {
                Log.e(TAG, "exception: " + arg0.getMessage());
            }
            if (arg1 == null) {
                Log.e(TAG, "webSocket == null");
            }
            
            arg0.printStackTrace();
            delayReConnect();
            return;
        }

        mWebSocket = arg1;
        mWebSocket.setClosedCallback(this);
        mWebSocket.setStringCallback(this);

        startKeepAlive();

    }

    /**
     * wait certain seconds, and then re-connect to server.
     */
    private void delayReConnect() {
        closeWebSocket();

        mWorkHandler.postDelayed(new Runnable() {

            @Override
            public void run() {
                buildServerConnection();
            }

        }, RETRY_PERIOD);
    }

    /**
     * start to connect to server.
     */
    private void buildServerConnection() {
        // new Thread(new UdpAddressResolverRunnable()).start();
        String url = DeathConfig.getServerUrl() + "/ws";
        startWebSocket(url);
    }

    /**
     * send message to peer.
     * 
     * @param msg
     *            message buffer
     */
    public void sendMessage(final byte[] msg) {
        if (mWebSocket != null) {
            mWebSocket.send(msg);
        }
    }

    /**
     * Send message to server.
     * @param msg
     * message.
     */
    public void sendMessage(final String msg) {
        if (mWebSocket != null) {
            Log.e(TAG, "send a message to server:" + msg);
            mWebSocket.send(msg);
        }
    }

    @Override
    public void onCompleted(final Exception arg0) {
        if (arg0 != null) {
            Log.e(TAG, "websocket connection broken:");
            arg0.printStackTrace();
        }

        delayReConnect();
    }

    @Override
    public void onStringAvailable(final String msg) {
        Log.e(TAG, "websocket recev msg: " + msg);
        
        mWorkHandler.post(new Runnable() {
            @Override
            public void run() {
                parseJSON(msg);
            }
            
        });
        
    }


    /**
     * Parser jsonString.
     * @param jsonStr
     * JsonString.
     */
    private void parseJSON(final String jsonStr) {
        if (null == jsonStr || jsonStr.equals("")) {
            return;
        }

        String type = null;

        try {
            JSONObject jsonObj = new JSONObject(jsonStr);
            if (jsonObj.has("type")) {
                type = jsonObj.getString("type");
            }

            if (type == null) {
                return;
            }

            NotifyListenerList listeners = mNotifyListeners.get(type);
            if (listeners == null) {
                Log.e(TAG, "no listener for json type:" + type);
                return;
            }

            for (NotifyListener ll : listeners.getList()) {
                ll.onJSONNotify(jsonObj);
            }

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }


    /**
     * PERIOD.
     */
    private static final int KEEP_ALIVE_PERIOD = (300 * 1000);
    
    /**
     * KEEP ALIVE RUNNABLE.
     */
    private final Runnable keepAliveRunnable = new Runnable() {

        @Override
        public void run() {
            sendMessage("hello server!");
            
            startKeepAlive();
        }
        
    };
    
     /**
     * create keep alive helper object.
     */
     private void startKeepAlive() {
         mWorkHandler.removeCallbacks(keepAliveRunnable);
         mWorkHandler.postDelayed(keepAliveRunnable, KEEP_ALIVE_PERIOD);
     }

    /**
     * add listener.
     * @param type type
     * @param listener listener
     */
    public void setNotifyListener(final String type, final NotifyListener listener) {
        NotifyListenerList list = mNotifyListeners.get(type);
        if (list == null) {
            list = new NotifyListenerList();
            mNotifyListeners.put(type, list);
        }
        
        list.addListener(listener);
        
    }
    
    /**
     * remove listener.
     * @param type type
     * @param listener listener
     */
    public void removeNotifyListener(final String type, final NotifyListener listener) {
        NotifyListenerList list = mNotifyListeners.get(type);
        if (list == null) {
            return;
        }
        
        list.removeListener(listener);
    }
}
